package cn.waleychain.exchange.service.impl.wallet.utils;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.CompletableFuture;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Service;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.Wallet;
import org.web3j.crypto.WalletFile;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.ObjectMapperFactory;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.DefaultBlockParameterNumber;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.utils.Convert.Unit;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.jsuportframework.util.JSUtils;

import cn.waleychain.exchange.core.logger.LoggerHelper;
import cn.waleychain.exchange.core.utils.CalendarUtils;
import cn.waleychain.exchange.feign.CoinServiceFeign;
import cn.waleychain.exchange.model.CoinInfo;
import cn.waleychain.exchange.model.CoinWallet;
import cn.waleychain.exchange.model.WalletCollectTask;
import cn.waleychain.exchange.service.impl.wallet.ethtoken.EthToken;
import cn.waleychain.exchange.service.impl.wallet.listener.EthTokenReceiveListener;
import cn.waleychain.exchange.service.wallet.CoinWalletService;
import rx.Subscription;

@Service
@PropertySource(value = { "classpath:application.yml" })
public class EthTokenCoinClient {
	
	private static final Logger mLog = LoggerFactory.getLogger(EthTokenCoinClient.class);
	
	@Autowired
    private CoinWalletService coinWalletService;
    @Autowired
    private CoinServiceFeign cionFeign;
    
    @Autowired
    private EthCoinClient ethCoinClient;

    private static final String OPT_ADDR = "0x831a60552100c728e8b3d0398983d17eabf0c73b";
    private static final String OPT_KEY = "{\"version\":3,\"crypto\":{\"mac\":\"87bdbbf8b0b075bff7f9562c111b0d9a20ae11e2d6a6df992aba7a9df6b2cb30\",\"cipherparams\":{\"iv\":\"c6ce65bf499413b221d76c5e08e6f6d2\"},\"kdfparams\":{\"dklen\":32,\"r\":8,\"salt\":\"48674be5bd74d74e5609948ff9e840285eb69dead12e0b8b62b7def0b13224ef\",\"p\":1,\"n\":262144},\"cipher\":\"aes-128-ctr\",\"ciphertext\":\"1558ecffdce31d21397a40d585089cfa78dec9c4184dfd2e2af06f64b0437e22\",\"kdf\":\"scrypt\"},\"id\":\"946b89d2-110a-462c-b0d8-77b6f2596543\",\"address\":\"831a60552100c728e8b3d0398983d17eabf0c73b\"}";
    private static final String OPT_PWD = "Cheng1230";

    @Value("${eth.keystore.path}")
    private String keystore;
    public static final double TRANSFER_FEE = 0.09D;
    public static final HashMap<String, String> tokens = new HashMap<>();
    public static final HashMap<String, BigDecimal> decimal = new HashMap<>();

    public EthTokenCoinClient() {
    }

    public BigDecimal getBalance(CoinInfo tradeCoin) {
        return this.getBalance(tradeCoin, (String) null);
    }

    public BigDecimal getBalance(CoinInfo tradeCoin, String addr) {
        try {
            Web3j web3j = EthCoinClient.getCoinClient(tradeCoin);
            EthToken abi = this.getAbi(tradeCoin.getName(), web3j);
            BigInteger balanceValue;
            if (JSUtils.ifStringEmpty(addr)) {
                balanceValue = abi.balanceOf(OPT_ADDR).send();
            } else {
                balanceValue = abi.balanceOf(addr).send();
            }
            BigDecimal balance = new BigDecimal(balanceValue).divide(decimal.get(tradeCoin.getName()));
            return balance;
        } catch (Exception e) {
            e.printStackTrace();
            LoggerHelper.printLogErrorNotThrows(mLog, e, e.getMessage());
            return new BigDecimal("-99");
        }
    }

    public boolean isValidAddress(CoinInfo tradeCoin, String addr) {
        return this.ethCoinClient.isValidAddress(tradeCoin, addr);
    }


    @SuppressWarnings("unused")
	public String sendToAddress(CoinInfo tradeCoin, Long userId, String to, BigDecimal amount) throws Exception {
        if (!WalletUtils.isValidAddress(to)) {
            throw new Exception("Invalid dest address!");
        } else {
            Web3j web3j = EthCoinClient.getCoinClient(tradeCoin);

            try {
                BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();

                EthToken abi = this.getAbi(tradeCoin.getName(), web3j, gasPrice);

                CompletableFuture<TransactionReceipt> transactionReceiptCompletableFuture = abi.transfer(to, amount.multiply(decimal.get(tradeCoin.getName())).toBigInteger()).sendAsync();
                String txid = abi.transfer(to, amount.multiply(decimal.get(tradeCoin.getName())).toBigInteger()).send().getTransactionHash();
                // String txid = (abi.transfer(new Address(to), new Uint256(amount.multiply(decimal.get(tradeCoin.getName())).toBigInteger())).get()).getTransactionHash();
                if (JSUtils.ifStringEmpty(txid)) {
                    throw new Exception("txid is empty!");
                } else {
                    return txid;
                }
            } catch (Exception var8) {
                var8.printStackTrace();
                throw new Exception("Send transaction error", var8);
            }
        }
    }


    public String sendToCenter(CoinInfo tradeCoin, Long userId, String addr, BigDecimal amount) throws Exception {
    	CoinWallet userEth = this.coinWalletService.fetchCoinWalletInfo(userId.longValue(), tradeCoin.getCoinId());
        if (userEth == null) {
            throw new Exception("User Eth info is null!");
        } else {
            try {
                Web3j web3j = EthCoinClient.getCoinClient(tradeCoin);
                Credentials credentials = WalletUtils.loadCredentials(JSUtils.BASE64Decode(userEth.getPassword()), new File(this.keystore + userEth.getKeystore()));

                BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();
                BigInteger maxPrice = EthToken.GAS_PRICE.multiply(BigInteger.valueOf(2L));
                if (gasPrice.compareTo(maxPrice) == 1) {
                    gasPrice = maxPrice;
                }
                BigInteger gasLimit = BigInteger.valueOf(100000L);

                EthToken abi = EthToken.load(tokens.get(tradeCoin.getName()), web3j, credentials, gasPrice, gasLimit);

                //EthToken abi = EthToken.load(tokens.get(tradeCoin.getName()), web3j, credentials, gasPrice, EthToken.GAS_LIMIT);
                String txid = abi.transfer(addr, amount.multiply(decimal.get(tradeCoin.getName())).toBigInteger()).send().getTransactionHash();
                if (JSUtils.ifStringEmpty(txid)) {
                    throw new Exception("txid is empty!");
                } else {
//                    this.log.info("Collect transaction hash:" + txid);
//
//                    try {
//                        txid = this.ethCoinClient.sendToCenter(tradeCoin, userId, null, BigDecimal.valueOf(0.089D));
//                        this.log.info("Return Master Eth transaction hash:" + txid);
//                    } catch (Exception var11) {
//                        this.log.error("Transfer To Master Eth Center address error:" + var11.getMessage());
//                    }

                    return txid;
                }
            } catch (Exception var12) {
                var12.printStackTrace();
                throw new Exception("Send transaction error", var12);
            }
        }
    }

    public void addCollectTask(CoinInfo tradeCoin, Long userId, BigDecimal amount) throws Exception {

        CoinWallet userEth = this.coinWalletService.fetchCoinWalletInfo(userId.longValue(), tradeCoin.getCoinId());
        if (userEth == null) {
            throw new Exception("User Eth info is null!");
        } else {
            LoggerHelper.printLogInfo(mLog, "default开始归账");
            String txid = this.ethCoinClient.sendSXFToAddress(tradeCoin, userId, userEth.getAddress(), BigDecimal.valueOf(0.005D));//修改这里为0.005Deth,原来写的是0.09D
            LoggerHelper.printLogInfo(mLog, "归账eth打出成功,txid=" + txid);
            WalletCollectTask task = new WalletCollectTask();
            task.setCoinId(tradeCoin.getCoinId());
            task.setFromUser(userId);
            task.setToAddr(OPT_ADDR);
            task.setAmount(amount);
            task.setExecTime(CalendarUtils.addMinute(new Date(), 30));
            
            this.coinWalletService.createWalletCollectTask(task);
        }
    }

    public void refreshAcceptor(CoinInfo tradeCoin) throws Exception {
        Web3j web3j = EthCoinClient.getCoinClient(tradeCoin);
        try {
            EthToken abi = this.getAbi(tradeCoin.getName(), web3j);
            LoggerHelper.printLogInfo(mLog, tradeCoin.getName() + " lastBlock:" + tradeCoin.getLastblock());
            DefaultBlockParameter startBlock = JSUtils.ifStringEmpty(tradeCoin.getLastblock()) ? DefaultBlockParameterName.EARLIEST : new DefaultBlockParameterNumber(Long.valueOf(tradeCoin.getLastblock()).longValue());
            LoggerHelper.printLogInfo(mLog, "=============" + startBlock.getValue());
            Subscription subscription = abi.transferEventObservable(startBlock, DefaultBlockParameterName.LATEST).subscribe(new EthTokenReceiveListener(tradeCoin.getCoinId(), tradeCoin.getName(), JSUtils.ifStringEmpty(tradeCoin.getLastblock()) ? 0 : Integer.parseInt(tradeCoin.getLastblock()), cionFeign, coinWalletService));
            WalletAcceptorUtil.instance.upEthTokenAcceptor(tradeCoin.getCoinId(), subscription);
        } catch (Exception e) {
            LoggerHelper.printLogErrorNotThrows(mLog, e, e.getMessage());
        }

    }

    private EthToken getAbi(String tokenName, Web3j web3j) throws Exception {
        return getAbi(tokenName, web3j, null);
    }


    private EthToken getAbi(String tokenName, Web3j web3j, BigInteger gasrice) throws Exception {
        try {
            ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
            WalletFile walletFile = objectMapper.readValue(OPT_KEY, WalletFile.class);
            Credentials credentials = Credentials.create(Wallet.decrypt(OPT_PWD, walletFile));
            return EthToken.load(tokens.get(tokenName), web3j, credentials, gasrice == null ? EthToken.GAS_PRICE : gasrice, EthToken.GAS_LIMIT);
        } catch (CipherException | IOException var6) {
            var6.printStackTrace();
            throw new Exception(var6.getMessage());
        }
    }

    static {
        tokens.put("CCB", "0x01C67791309c71aA4Ed373025a0C089696D7c9e4");
        decimal.put("CCB", Unit.ETHER.getWeiFactor());

        tokens.put("ATM", "0x9B11EFcAAA1890f6eE52C6bB7CF8153aC5d74139");
        decimal.put("ATM", BigDecimal.TEN.pow(8));

        tokens.put("PIX", "0x8eFFd494eB698cc399AF6231fCcd39E08fd20B15");
        decimal.put("PIX", BigDecimal.TEN.pow(0));

        tokens.put("MVC", "0xb17df9a3b09583a9bdcf757d6367171476d4d8a3");
        decimal.put("MVC", Unit.ETHER.getWeiFactor());

        tokens.put("CAN", "0x5f3789907b35DCe5605b00C0bE0a7eCDBFa8A841");
        decimal.put("CAN", Unit.ETHER.getWeiFactor());

        tokens.put("EOS", "0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0");
        decimal.put("EOS", Unit.ETHER.getWeiFactor());

        tokens.put("hht", "0x190289e0f72DFD611121da7DD9F3e6C92b8F71e4");
        decimal.put("hht", Unit.ETHER.getWeiFactor());

        tokens.put("AE", "0x5ca9a71b1d01849c0a95490cc00559717fcf0d1d");
        decimal.put("AE", Unit.ETHER.getWeiFactor());

        tokens.put("QASH", "0x618e75ac90b12c6049ba3b27f5d5f8651b0037f6");
        decimal.put("QASH", BigDecimal.TEN.pow(6));

        tokens.put("FTB", "0x0Fa9198BB9cA66260Fa9A182f27ebBaAB55430ff");
        decimal.put("FTB", Unit.ETHER.getWeiFactor());

        tokens.put("CCT", "0x336F646F87D9f6bC6Ed42Dd46E8b3fD9DbD15C22");
        decimal.put("CCT", Unit.ETHER.getWeiFactor());

        tokens.put("SFC", "0xcA6aA46F253D553e752F4Bf6BC977555225cDA8A");
        decimal.put("SFC", Unit.ETHER.getWeiFactor());

        tokens.put("OEC", "0x0f7bA0f783B2210fc7c0dE8d0020a9d8743BD842");
        decimal.put("OEC", Unit.ETHER.getWeiFactor());

        tokens.put("RCT", "0x13f25cd52b21650caa8225c9942337d914c9b030");
        decimal.put("RCT", Unit.ETHER.getWeiFactor());

        tokens.put("SHOW", "0x58d0A58E4B165a27E4e1B8C2A3eF39C89b581180");
        decimal.put("SHOW", Unit.ETHER.getWeiFactor());

        tokens.put("EGT", "0x15b683f780736217a34a058ec8179595e8a84c5b");
        decimal.put("EGT", BigDecimal.TEN.pow(8));

        tokens.put("BKEX", "0x45245bc59219eeaaf6cd3f382e078a461ff9de7b");
        decimal.put("BKEX", BigDecimal.TEN.pow(4));

        tokens.put("TCASH", "0xb8742486c723793cf5162bb5d3425ed9cd73d049");
        decimal.put("TCASH", Unit.ETHER.getWeiFactor());

        tokens.put("GTC", "0xB70835D7822eBB9426B56543E391846C107bd32C");
        decimal.put("GTC", Unit.ETHER.getWeiFactor());

        tokens.put("SMT", "0x55f93985431fc9304077687a35a1ba103dc1e081");
        decimal.put("SMT", Unit.ETHER.getWeiFactor());

    }
}
